/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */

package ytmc.uploader;

import com.google.gdata.client.media.ResumableGDataFileUploader;
import com.google.gdata.client.uploader.ProgressListener;
import com.google.gdata.client.uploader.ResumableHttpFileUploader;
import com.google.gdata.client.youtube.YouTubeService;
import com.google.gdata.data.media.MediaFileSource;
import com.google.gdata.data.media.mediarss.MediaCategory;
import com.google.gdata.data.media.mediarss.MediaDescription;
import com.google.gdata.data.media.mediarss.MediaKeywords;
import com.google.gdata.data.media.mediarss.MediaTitle;
import com.google.gdata.data.youtube.VideoEntry;
import com.google.gdata.data.youtube.YouTubeMediaGroup;
import com.google.gdata.data.youtube.YouTubeNamespace;
import com.google.gdata.util.ServiceException;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.io.File;
import java.io.IOException;
import java.net.URL;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.concurrent.Executors;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.activation.MimetypesFileTypeMap;
import ytmc.TimePeriod;
import ytmc.TransferSpeed;

/**
 *
 * @author K
 */
public class UploadTaskRunner
{
    private final Object lock = new Object();
    private final String uploadUrl = "http://uploads.gdata.youtube.com/resumable/feeds/api/users/default/uploads";
    private final long chunkSize = 1024 * 4;
    private final int updateInterval = 1000;
    private YouTubeService service;

    private UploadTask task;

    public UploadTask getTask()
    {
        return task;
    }

    private boolean finished;

    public boolean isFinished()
    {
        synchronized (lock)
        {
            return finished;
        }
    }

    private void setFinished(boolean finished)
    {
        synchronized (lock)
        {
            boolean oldValue = this.finished;
            this.finished = finished;
            notifyPropertyChangeListeners(new PropertyChangeEvent(this, "finished", oldValue, finished));
        }
    }

    private ArrayList<PropertyChangeListener> listeners;

    public void addPropertyChangeListener(PropertyChangeListener listener)
    {
        synchronized (lock)
        {
            if (!listeners.contains(listener))
            {
                listeners.add(listener);
            }
        }
    }

    public void removePropertyChangeListener(PropertyChangeListener listener)
    {
        synchronized (lock)
        {
            if (listeners.contains(listener))
            {
                listeners.remove(listener);
            }
        }
    }

    private void notifyPropertyChangeListeners(PropertyChangeEvent event)
    {
        for (PropertyChangeListener propertyChangeListener : listeners)
        {
            propertyChangeListener.propertyChange(event);
        }
    }

    public void uploadAsync()
    {
        setFinished(false);

        VideoEntry newEntry = new VideoEntry();

        YouTubeMediaGroup mg = newEntry.getOrCreateMediaGroup();

        mg.setTitle(new MediaTitle());
        mg.getTitle().setPlainTextContent(task.getTitle());

        mg.addCategory(new MediaCategory(YouTubeNamespace.CATEGORY_SCHEME, task.getCategory().getYouTubeName()));

        mg.setDescription(new MediaDescription());
        mg.getDescription().setPlainTextContent(task.getDescription());

        mg.setKeywords(new MediaKeywords());
        String[] keywords = task.getTags().split("\\s+");
        mg.getKeywords().addKeywords(Arrays.asList(keywords));

        mg.setPrivate(false);

        File file = new File(task.getFilePath());
        MediaFileSource ms = new MediaFileSource(file, new MimetypesFileTypeMap().getContentType(file));
        newEntry.setMediaSource(ms);

        try
        {
            ResumableGDataFileUploader uploader = new ResumableGDataFileUploader.Builder(service, new URL(uploadUrl), ms, newEntry)
                                                                                .chunkSize(chunkSize)
                                                                                .title(task.getTitle())
                                                                                .trackProgress(new UploadProgressListener(), updateInterval)
                                                                                .executor(Executors.newFixedThreadPool(1))
                                                                                .build();
            uploader.start();
        }
        catch (ServiceException ex)
        {
            Logger.getLogger(UploadTaskRunner.class.getName()).log(Level.SEVERE, null, ex);
        }
        catch (IOException ex)
        {
            Logger.getLogger(UploadTaskRunner.class.getName()).log(Level.SEVERE, null, ex);
        }
    }

    UploadTaskRunner(UploadTask task, YouTubeService service)
    {
        this.task = task;
        this.service = service;
        this.listeners = new ArrayList<PropertyChangeListener>();
    }

    private class UploadProgressListener implements ProgressListener
    {
        @Override
        public void progressChanged(ResumableHttpFileUploader uploader)
        {
            switch(uploader.getUploadState())
            {
                case COMPLETE:
                    onComplete(uploader);
                    break;
                case CLIENT_ERROR:
                    onError(uploader);
                    break;
                case IN_PROGRESS:
                    onProgressChanged(uploader);
                    break;
                case NOT_STARTED:
                    onNotStarted(uploader);
                    break;
            }
        }

        private void onComplete(ResumableHttpFileUploader uploader)
        {
            task.setProgress(100);
            task.setUploadSpeed(new TransferSpeed(0));
            task.setRemainingTime(new TimePeriod(0));
            task.setStatus(UploadTaskStatus.FINISHED);

            notifyPropertyChangeListeners(new PropertyChangeEvent(this, "task", task, task));

            setFinished(true);
        }

        private void onError(ResumableHttpFileUploader uploader)
        {
            task.setUploadSpeed(new TransferSpeed(0));
            task.setRemainingTime(new TimePeriod(0));
            task.setStatus(UploadTaskStatus.FAILED);

            notifyPropertyChangeListeners(new PropertyChangeEvent(this, "task", task, task));

            setFinished(true);
        }

        private long currentUpdateBytesCount = 0;
        private long previousUpdateBytesCount = 0;
        private long fileSize = (new File(task.getFilePath())).length();

        private void onProgressChanged(ResumableHttpFileUploader uploader)
        {
            task.setProgress((int) (uploader.getProgress() * 100));
            task.setStatus(UploadTaskStatus.RUNNING);

            currentUpdateBytesCount = uploader.getNumBytesUploaded();
            long uploadedBytes = currentUpdateBytesCount - previousUpdateBytesCount;
            task.setUploadSpeed(new TransferSpeed(uploadedBytes));
            if(uploadedBytes > 0)
            {
                task.setRemainingTime(new TimePeriod((int)Math.ceil((double)(fileSize - currentUpdateBytesCount) / (double)uploadedBytes)));
            }
            previousUpdateBytesCount = currentUpdateBytesCount;

            notifyPropertyChangeListeners(new PropertyChangeEvent(this, "task", task, task));
        }

        private void onNotStarted(ResumableHttpFileUploader uploader)
        {
            setFinished(false);

            task.setStatus(UploadTaskStatus.NOT_STARTED);
        }
    }
}
